using System;
using System.Collections.Generic;
using System.IO;
using System.Xml;
using System.Xml.Serialization;
using System.Net;
using Microsoft.Xml.XMLGen;

namespace MH.WsdlWorks.ProjectModel
{
    [XmlType]
    public class Request : DirectoryNode
    {
        const string requestFileName = "Request.xml";
        const string responseFileName = "Response";
        const int bufferSize = 0x1000;    // 4K buffer size for reading/writing files

        public Request() { }
        public Request(Operation parentOperation, string name, MessageSchemaInfo messageSchemaInfo)
            : base(parentOperation, name)
        {
            this.serviceUrl = parentOperation.ServiceUrl;
            CreateRequestMessage(messageSchemaInfo);
        }

        private void CreateRequestMessage(MessageSchemaInfo messageSchemaInfo)
        {
            this.requestFile = new MessageFile(this, requestFileName);
            System.Diagnostics.Debug.Assert(!File.Exists(this.requestFile.GetPath()), "requestFilePath already exists");

            // use XmlSampleGenerator to create an example request
            XmlSampleGenerator xmlSampleGenerator = 
                new XmlSampleGenerator(messageSchemaInfo.Schemas, messageSchemaInfo.RootElement);
            using(XmlWriter writer = XmlWriter.Create(this.requestFile.GetPath()))
            {
                writer.WriteStartElement("soap", "Envelope", "http://schemas.xmlsoap.org/soap/envelope/");
                writer.WriteStartElement("soap", "Body", "http://schemas.xmlsoap.org/soap/envelope/");
                xmlSampleGenerator.WriteXml(writer);
                writer.WriteEndElement();
                writer.WriteFullEndElement();
            }
        }

        string serviceUrl;
        [XmlAttribute]
        public string ServiceUrl
        {
            get { return serviceUrl; }
            set { serviceUrl = value; }
        }

        MessageFile requestFile;
        [XmlElement]
        public MessageFile RequestFile
        {
            get { return requestFile; }
            set { requestFile = value; }
        }

        List<MessageFile> responseFiles = new List<MessageFile>();
        [XmlArray]
        public List<MessageFile> ResponseFiles
        {
            get { return responseFiles; }
            set { responseFiles = value; }
        }

        public override void SetParent(INode parent)
        {
            this.Parent = parent;
            if (requestFile != null)
            {
                requestFile.SetParent(this);
            }
            foreach(MessageFile responseFile in this.responseFiles)
            {
                responseFile.SetParent(this);
            }
        }

        private Operation ParentOperation
        {
            get
            {
                return (Operation)this.Parent;
            }
        }

        /// <summary>
        /// Executed when the user calls 'Run' from the Request context menu. 
        /// Calls the web service at this.ServiceUrl with the Request.xml file.
        /// </summary>
        public MessageFile RunRequest()
        {
            HttpWebRequest webRequest = CreateWebRequest(this.serviceUrl, this.ParentOperation.SoapAction);
            InsertRequestSoapEnvelope(webRequest);

            try
            {
                using (WebResponse webResponse = webRequest.GetResponse())
                {
                    return WriteResponseToFile(webResponse);
                }
            }
            catch (WebException webException)
            {
                if (webException.Response == null) throw;
                using (WebResponse webResponse = webException.Response)
                {
                    return WriteResponseToFile(webResponse);
                }
            }
        }

        /// <summary>
        /// Create an HttpWebRequest object for calling a web service
        /// </summary>
        /// <param name="url">the url of the web service</param>
        /// <param name="action">the SoapAction to execute</param>
        /// <returns>an HttpWebRequest instance</returns>
        private static HttpWebRequest CreateWebRequest(string url, string action)
        {
            HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(url);
            webRequest.Headers.Add("SOAPAction", action);
            webRequest.ContentType = "text/xml;charset=\"utf-8\"";
            webRequest.Accept = "text/xml";
            webRequest.Method = "POST";
            return webRequest;
        }

        /// <summary>
        /// Stream the request file into the request buffer
        /// </summary>
        /// <param name="webRequest"></param>
        private void InsertRequestSoapEnvelope(HttpWebRequest webRequest)
        {
            byte[] buffer = new byte[bufferSize];
            int bytesRead;

            using (FileStream fileStream = File.OpenRead(this.requestFile.GetPath()))
            using (Stream requestStream = webRequest.GetRequestStream())
            {
                while ((bytesRead = fileStream.Read(buffer, 0, bufferSize)) != 0)
                {
                    requestStream.Write(buffer, 0, bytesRead);
                }
                requestStream.Flush();
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="WebResponse"></param>
        private MessageFile WriteResponseToFile(WebResponse WebResponse)
        {
            string uniqueResponseFileName = Utilities.GetUniqueFileName(GenerateResponseFileName(), this.GetPath());
            MessageFile responseFile = new MessageFile(this, uniqueResponseFileName);

            byte[] buffer = new byte[bufferSize];
            int bytesRead;

            using (FileStream fileStream = File.Create(responseFile.GetPath()))
            using (Stream responseStream = WebResponse.GetResponseStream())
            {
                while ((bytesRead = responseStream.Read(buffer, 0, bufferSize)) != 0)
                {
                    fileStream.Write(buffer, 0, bytesRead);
                }
                fileStream.Flush();
            }

            this.responseFiles.Add(responseFile);
            return responseFile;
        }

        private string GenerateResponseFileName()
        {
            return responseFileName + DateTime.Now.ToString("_yyyy_MM_dd__HH_mm_ss_fff") + ".xml";
        }
    }
}
